import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import cvxpy as cp
import scipy.stats as ss
import seaborn as sns
import warnings
from scipy.optimize import Bounds, LinearConstraint, minimize
from IPython.display import display
import yfinance as yf
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
from scipy.stats import norm
# Define custom colors
gray = 'rgb(193,188,188)'
purple = 'rgb(120,3,150)'
gold = 'rgb(214,194,146)'
brown = 'rgb(150,29,33)'
blue = 'rgb(184,216,220)'
blue_dark = 'rgb(60,116,123)'
grey_dark = 'rgb(91,85,85)'
purple_light = 'rgb(210,154,243)'
gold_dark = 'rgb(128,101,39)'
black = 'rgb(0,0,0)'
light_purple = 'rgb(180, 150, 200)'
deep_gold = 'rgb(176, 145, 50)'
dark_grey = 'rgb(77, 77, 77)'
olive_green = 'rgb(128, 128, 0)'
soft_blue = 'rgb(150, 190, 220)'
rich_brown = 'rgb(139, 69, 19)'
slate_blue = 'rgb(106, 90, 205)'
turquoise = 'rgb(64, 224, 208)'
midnight_blue = 'rgb(25, 25, 112)'
# Color
colors = [purple, gray, gold, brown, blue, blue_dark, grey_dark, purple_light, gold_dark, black,
light_purple, deep_gold, dark_grey, olive_green, soft_blue, rich_brown, slate_blue, turquoise, midnight_blue]
pio.templates["myname"] = go.layout.Template(layout=go.Layout(colorway=colors))
pio.templates["Neoma_template"] = go.layout.Template(
layout=dict(
legend_title_text='',
plot_bgcolor='white',
paper_bgcolor='white'
)
)
# Set the default template
pio.templates.default = "plotly+Neoma_template+myname"
tickers = {
'SP500': '^GSPC',
'EuroStoxx50': '^STOXX50E',
'US_Treasury_Bond_ETF': 'TLT',
'Corporate_Bond_ETF': 'LQD',
'NASDAQ': '^IXIC',
'DAX': '^GDAXI',
'CAC40': '^FCHI',
'FTSE100': '^FTSE',
'Nikkei225': '^N225',
'HangSeng': '^HSI',
'ShanghaiComposite': '000001.SS',
'BSESENSEX': '^BSESN',
'ASX200': '^AXJO',
'High_Yield_Bond': 'HYG',
'Emerging_Markets_Bond': 'EMB',
'US_Treasury_Bond': 'TLT',
'Corporate_Bond': 'LQD',
'Dow_Jones': '^DJI',
'Russell_2000': '^RUT',
'Emerging_Markets_Index': 'EEM',
'Total_Stock_Market': 'VTI',
'Gold': 'GLD',
'Silver': 'SLV',
'Crude_Oil': 'USO',
'S&P_MidCap_400': '^MID',
'FTSE_Emerging': 'VWO',
'Total_Bond_Market': 'BND',
'MSCI_World': 'URTH',
'Biotechnology': 'IBB',
'Real_Estate': 'VNQ',
'Consumer_Staples': 'XLP',
'Technology': 'XLK',
'Health_Care': 'XLV',
'Utilities': 'XLU',
'Energy': 'XLE',
'Financials': 'XLF',
'Brazil_Stock': 'EWZ',
'Mexico_Stock': 'EWW',
'Canada_Stock': 'EWC'
}
Equity = ['^GSPC','^STOXX50E','^IXIC','^GDAXI','^FCHI','^FTSE','^N225','^HSI','000001.SS','^BSESN',
'^AXJO','^DJI','^RUT','EEM','VTI', '^MID','VWO','URTH','IBB','VNQ','XLP','XLK','XLV','XLU','XLE',
'XLF','EWZ','EWW','EWC']
Bonds= ['TLT', 'LQD', 'HYG', 'EMB', 'BND']
Commodities=['GLD', 'SLV', 'USO']
print(len(tickers),'assets')
39 assets
#download data from Yfinance API
df = pd.DataFrame()
for index_name, ticker in tickers.items():
df_ = yf.download(ticker, progress=False)
df[index_name] = df_['Close']
df.dropna(inplace=True)
fig = go.Figure()
#SP500 and EuroStoxx50, which will use the left y-axis by default
fig.add_trace(go.Scatter(x=df.index, y=df['SP500'], name='SP500', line=dict(width=1)))
fig.add_trace(go.Scatter(x=df.index, y=df['EuroStoxx50'], name='EuroStoxx50', line=dict(width=1)))
#US_Treasury_Bond_ETF and Corporate_Bond_ETF, which will use the right y-axis
fig.add_trace(go.Scatter(x=df.index, y=df['US_Treasury_Bond_ETF'], name='US Treasury Bond ETF', yaxis='y2', line=dict(width=1)))
fig.add_trace(go.Scatter(x=df.index, y=df['Corporate_Bond_ETF'], name='Corporate Bond ETF', yaxis='y2', line=dict(width=1)))
# Update layout for primary y-axis (left side)
fig.update_layout(
yaxis=dict(
title='Stock Indices'
)
)
# Update layout for secondary y-axis (right side)
fig.update_layout(
yaxis2=dict(
title='Bond ETFs',
overlaying='y',
side='right'
),
xaxis=dict(
title='Date'
),
legend_title_text='Assets'
)
fig.update_layout(
legend=dict(
title=dict(text='Assets'),
yanchor="top",
y=0.95,
xanchor="left",
x=0.1
),
title='Comparison of Stock Indices and Bond ETFs'
)
# Show the figure
fig.show()
fig.write_html('Comparison_Viz.html')
def cumulative_return(df):
return (1 + df).cumprod()
df_returns = df.pct_change().dropna()
dated_returns = df_returns.copy()
dated_returns['year_week'] = dated_returns.index.to_period('W')
dated_returns['year_month'] = dated_returns.index.to_period('M')
dated_returns['year_quarter'] = dated_returns.index.to_period('Q')
dated_returns['year'] = dated_returns.index.year
annual_cum=dated_returns.drop(columns=['year_week', 'year_month', 'year_quarter']).groupby(['year']).apply(cumulative_return).drop(columns=['year'])
week_cum=dated_returns.drop(columns=['year_month', 'year_quarter','year']).groupby(['year_week']).apply(cumulative_return)
month_cum=dated_returns.drop(columns=['year_week', 'year_quarter','year']).groupby(['year_month']).apply(cumulative_return)
quarter_cum=dated_returns.drop(columns=['year_week', 'year_month','year']).groupby(['year_quarter']).apply(cumulative_return)
mean_fig= px.bar((df_returns.describe().loc['mean']*100).sort_values(ascending=False))
mean_fig.update_layout(yaxis_title="")
mean_fig.update_layout(xaxis_title="")
mean_fig.update_layout(legend_title_text='',title='Mean of daily returns (%)',showlegend=False,title_x=0.5)
annual_return = annual_cum.groupby('year').last()-1
sorted_data=(df_returns.describe().loc['mean']*100).sort_values(ascending=False)
mean_value = sorted_data.mean()
median_value = sorted_data.median()
max_value = sorted_data.max()
min_value = sorted_data.min()
mean_fig = px.bar(sorted_data, text=sorted_data)
mean_fig.update_traces(texttemplate='%{text:.2f}', textposition='inside',name='Average Yearly Returns',)
mean_fig.update_layout(
yaxis_title="",
xaxis_title="",
legend_title_text='',
title='Mean of Yearly Returns (%)',
title_x=0.5,
)
mean_fig.add_trace(
go.Scatter(
x=sorted_data.index,
y=[mean_value] * len(sorted_data),
mode='lines',
name='Mean',
line=dict(color=blue, dash='dash'),
)
)
mean_fig.add_trace(
go.Scatter(
x=sorted_data.index,
y=[median_value] * len(sorted_data),
mode='lines',
name='Median',
line=dict(color=brown, dash='dot')
)
)
mean_fig.add_trace(
go.Scatter(
x=[sorted_data.idxmax(), sorted_data.idxmin()],
y=[max_value, min_value],
mode='markers',
marker=dict(color=gold, size=5),
name='Extrems'
)
)
mean_fig.show()
# Sauvegarde du graphique dans un fichier HTML
mean_fig.write_html('yearly_returns.html')
annual_return = annual_cum.groupby('year').last()
fig_year=px.line(annual_return)
fig_year.update_layout(yaxis_title="")
fig_year.update_layout(xaxis_title="")
fig_year.update_layout(legend_title_text='Assets')
quarter_return = quarter_cum.groupby('year_quarter').last()
fig_quarter=px.line(quarter_return.reset_index(drop=True))
fig_quarter.update_layout(yaxis_title="")
fig_quarter.update_layout(xaxis_title="")
fig_quarter.update_layout(legend_title_text='Assets')
fig_quarter.update_traces(line=dict(width=2))
df_perf_cumul = (1+df_returns).cumprod()-1
fig=px.line(df_perf_cumul)
fig.update_layout(yaxis_title="")
fig.update_layout(xaxis_title="")
fig.update_layout(legend_title_text='')
fig.update_traces(line=dict(width=1))
trading_days=252
risk_free_rate=.02
performance_metrics = {
'Annualized Return (%)': {},
'Annualized Volatility (%)': {},
'Sharpe Ratio (%)': {}
}
# Calculate metrics for each asset
for column in df_returns.columns:
# Annualized Return
cum_return = (1 + df_returns[column]).cumprod().iloc[-1]
annualized_return = cum_return**(trading_days / len(df_returns)) - 1
performance_metrics['Annualized Return (%)'][column] = round(annualized_return*100,1)
# Annualized Volatility
annualized_volatility = df_returns[column].std() * np.sqrt(trading_days)
performance_metrics['Annualized Volatility (%)'][column] = round(annualized_volatility*100,1)
# Sharpe Ratio
sharpe_ratio = (annualized_return - risk_free_rate) / annualized_volatility
performance_metrics['Sharpe Ratio (%)'][column] = round(sharpe_ratio*100,1)
performance_df = pd.DataFrame(performance_metrics)
fig = go.Figure(data=[go.Table(
header=dict(values=['Index'] + list(performance_df.columns),
fill_color=blue,
align='center'),
cells=dict(values=[performance_df.index] + [performance_df[k].tolist() for k in performance_df.columns],
fill_color='white',
align='center'))
])
fig.show()
fig.write_html('tab_assets.html')
corr_matrix = df_returns.corr()
# Création du heatmap avec le thème personnalisé
fig = px.imshow(
corr_matrix,
text_auto=True,
labels=dict(x='Variable', y='Variable', color=''),
color_continuous_scale='Purples'
)
# Remove the axis titles
fig.update_layout(
xaxis_title='',
yaxis_title='',
)
fig.update_xaxes(tickfont=dict(size=8))
fig.update_yaxes(tickfont=dict(size=8))
fig.show()
fig.write_html('cov.html')
index = df.copy()
weights = index.copy()
weights['Total']=weights.sum(axis=1)
for col in weights.columns:
weights[col] = weights[col].div(weights['Total'])
weights=weights.drop(columns=['Total'])
index_price_weighted = (df_returns * weights).sum(axis=1)
index_equal_weighted = (df_returns / len(df_returns.columns)).sum(axis=1)
fig = px.line()
fig.update_layout(
xaxis_title='',
yaxis_title='',
legend_title_text='',
showlegend=False,
title = 'Returns of price and equaly weighted index over time',
title_x=0.5
)
fig.add_scatter(x=index_equal_weighted.index,y=index_equal_weighted,line=dict(width=.5,color=purple))
fig.add_scatter(x=index_price_weighted.index,y=index_price_weighted,line=dict(width=.5,color=blue))
print('Correlation of price & equal weighted index is',round(index_price_weighted.corr(index_equal_weighted),3))
Correlation of price & equal weighted index is 0.847
df_perf_cumul_index_price_weighted = (1+index_price_weighted).cumprod()
df_perf_cumul_index_equal_weighted = (1+index_equal_weighted).cumprod()
fig_cum_perf = px.line()
fig_cum_perf.update_layout(
xaxis_title='',
yaxis_title='',
legend_title_text='',
showlegend=True,
title = 'Cumulative returns',
title_x=0.4
)
fig_cum_perf.add_scatter(x=df_perf_cumul_index_price_weighted.index,y=df_perf_cumul_index_price_weighted,line=dict(width=2.5,color=purple), name='Price Weighted Index' )
fig_cum_perf.add_scatter(x=df_perf_cumul_index_equal_weighted.index,y=df_perf_cumul_index_equal_weighted,line=dict(width=2.5,color=blue), name='Equal Weighted Index' )
fig_cum_perf.add_scatter(x=df_returns.index,y=(1+df_returns['SP500']).cumprod(),line=dict(width=2.5,color=gray),name="S&P 500 Index")
trading_days=252
# Annualized Return
annualized_return_price_weighted = df_perf_cumul_index_price_weighted.iloc[-1]**(trading_days / len(df_perf_cumul_index_price_weighted))-1
annualized_return_equal_weighted = df_perf_cumul_index_equal_weighted.iloc[-1]**(trading_days / len(df_perf_cumul_index_equal_weighted))-1
# Annualized Volatility
annualized_volatility_price_weighted = index_price_weighted.std() * np.sqrt(trading_days)
annualized_volatility_equal_weighted = index_equal_weighted.std() * np.sqrt(trading_days)
risk_free_rate = 0.02
# Sharpe Ratio
sharpe_ratio_price_weighted = (annualized_return_price_weighted - risk_free_rate) / annualized_volatility_price_weighted
sharpe_ratio_equal_weighted = (annualized_return_equal_weighted - risk_free_rate) / annualized_volatility_equal_weighted
data = {
'Metric': ['Annualized Return', 'Annualized Volatility', 'Sharpe Ratio'],
'Price Weighted Index': [
round(annualized_return_price_weighted,3),
round(annualized_volatility_price_weighted,3),
round(sharpe_ratio_price_weighted,3)
],
'Equal Weighted Index': [
round(annualized_return_equal_weighted,3),
round(annualized_volatility_equal_weighted,3),
round(sharpe_ratio_equal_weighted,3)
]
}
results_df = pd.DataFrame(data)
# Create headers
headers = results_df.columns
# Create cells
cells = [results_df[col] for col in results_df.columns]
# Create the table figure
fig = go.Figure(data=[go.Table(
header=dict(values=headers, fill_color=blue, align='center'),
cells=dict(values=cells, fill_color='white', align='center'))
])
# Update layout if needed
fig.update_layout(
title='Performance Metrics Comparison',
title_x=0.5
)
# Show the table
fig.show()
fig.write_html('tab_index.html')
#calculate rolling volatility
index_price_weighted_rolling_volatility = index_price_weighted.rolling(window=252).std()* (252**0.5)
index_equal_weighted_rolling_volatility = index_equal_weighted.rolling(window=252).std()* (252**0.5)
sp_rolling_volatility =df_returns['SP500'].rolling(window=252).std()
sp_rolling_volatility = sp_rolling_volatility* (252**0.5)
fig_vol = px.line()
fig_vol.update_layout(
xaxis_title='',
yaxis_title='',
legend_title_text='',
showlegend=True,
title = 'Volatilities',
title_x=0.4
)
fig_vol.add_scatter(x=index_price_weighted_rolling_volatility.index,y=index_price_weighted_rolling_volatility,line=dict(width=2.5,color=purple), name='Price Weighted Index Volatility' )
fig_vol.add_scatter(x=index_equal_weighted_rolling_volatility.index,y=index_equal_weighted_rolling_volatility,line=dict(width=2.5,color=blue), name='Equal Weighted Index Volatility' )
fig_vol.add_scatter(x=sp_rolling_volatility.index,y=sp_rolling_volatility,line=dict(width=2.5,color=gray), name='S&P 500 Volatility' )
fig_vol.show()
from plotly.subplots import make_subplots
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1)
fig.add_trace(
go.Scatter(x=df_perf_cumul_index_price_weighted.index, y=df_perf_cumul_index_price_weighted,
line=dict(width=2, color=purple), name='Price Weighted Index'),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=df_perf_cumul_index_equal_weighted.index, y=df_perf_cumul_index_equal_weighted,
line=dict(width=2, color=blue), name='Equal Weighted Index'),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=df_returns.index, y=(1 + df_returns['SP500']).cumprod(),
line=dict(width=2, color=gray), name='S&P 500 Index'),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=index_price_weighted_rolling_volatility.index, y=index_price_weighted_rolling_volatility,
line=dict(width=2, color=purple), showlegend=False),
row=2, col=1
)
fig.add_trace(
go.Scatter(x=index_equal_weighted_rolling_volatility.index, y=index_equal_weighted_rolling_volatility,
line=dict(width=2, color=blue), showlegend=False),
row=2, col=1
)
fig.add_trace(
go.Scatter(x=sp_rolling_volatility.index, y=sp_rolling_volatility,
line=dict(width=2, color=gray), showlegend=False),
row=2, col=1
)
fig.update_xaxes(title_text="Date", row=2, col=1)
fig.update_yaxes(title_text="Cumulative Returns", row=1, col=1)
fig.update_yaxes(title_text="Volatility", row=2, col=1)
fig.update_layout(
title='Cumulative Returns and Volatility',
showlegend=True
)
fig.update_layout(
legend=dict(
title=dict(text='Assets'),
yanchor="top",
y=1.05,
xanchor="left",
x=0.1
),
)
fig.show()
fig.write_html('PerfvsVol.html')
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# Standardizing the Data
scaler = StandardScaler()
df_standardized = scaler.fit_transform(df_returns)
# Applying PCA
pca = PCA()
principal_components = pca.fit_transform(df_standardized)
# Creating a DataFrame with the principal components
df_pca = pd.DataFrame(data=principal_components, columns=[f'PC{i+1}' for i in range(len(tickers))])
# Eigenvalues and Explained Variance
eigenvalues = pca.explained_variance_
explained_variance_ratio = pca.explained_variance_ratio_
df_pca
| PC1 | PC2 | PC3 | PC4 | PC5 | PC6 | PC7 | PC8 | PC9 | PC10 | ... | PC30 | PC31 | PC32 | PC33 | PC34 | PC35 | PC36 | PC37 | PC38 | PC39 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.859130 | 1.444826 | 0.659095 | 1.570960 | -0.362023 | -0.550282 | -0.724887 | 1.005993 | -0.419641 | -0.370420 | ... | 0.246101 | 0.183381 | -0.034044 | 0.086027 | -0.043694 | -0.022442 | -0.019814 | -0.024005 | -3.755720e-17 | 4.059021e-16 |
| 1 | -3.298139 | 0.519798 | 3.112211 | -0.593783 | 0.256638 | 0.595039 | 0.911917 | -1.176285 | 1.136450 | -0.707256 | ... | -0.365319 | 0.225073 | -0.063006 | -0.014009 | 0.142168 | -0.078668 | -0.127745 | 0.021319 | -5.791242e-16 | 1.343826e-16 |
| 2 | -3.890091 | -0.882904 | -0.917786 | -1.825813 | 0.024901 | 0.480793 | -0.090143 | -0.833311 | -2.000009 | 0.547554 | ... | -0.401376 | -0.241653 | -0.040494 | 0.044820 | -0.037231 | -0.143978 | 0.021422 | 0.005833 | -1.142501e-14 | 1.520659e-14 |
| 3 | -2.874354 | -1.527866 | 1.669119 | 0.170135 | 0.054672 | 0.574762 | 0.397005 | -0.410502 | -0.006726 | -0.295169 | ... | 0.627556 | -0.155270 | -0.035335 | 0.142130 | -0.039075 | 0.057401 | -0.056934 | -0.056040 | 1.515724e-14 | 1.147033e-14 |
| 4 | -0.505441 | -0.831478 | 0.615040 | -1.743223 | -1.113853 | -1.131273 | 1.388788 | 0.439262 | -0.425518 | -0.087200 | ... | -0.000578 | 0.358909 | 0.147673 | -0.223707 | -0.071499 | -0.042311 | -0.080806 | -0.041612 | -4.478369e-17 | 1.266929e-17 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2422 | 0.948352 | 0.888806 | 0.286245 | 0.752090 | 0.221441 | 0.991386 | 0.335523 | 0.970655 | -0.239256 | 0.471621 | ... | -0.015404 | 0.237899 | 0.391853 | -0.004212 | -0.133683 | 0.040958 | 0.138394 | 0.025687 | 1.385739e-16 | 7.345644e-17 |
| 2423 | -1.171213 | -0.958375 | 0.304983 | -0.250319 | -0.563028 | -0.322858 | -0.181520 | -0.517524 | 0.561047 | -0.153803 | ... | -0.217829 | -0.086315 | 0.059690 | -0.180764 | -0.036040 | -0.013145 | 0.138002 | 0.049390 | -3.162804e-17 | 3.165566e-17 |
| 2424 | 1.758841 | 1.074077 | -1.106840 | 0.969284 | 1.248482 | -0.910989 | 0.367836 | 1.006763 | 0.169613 | 0.465431 | ... | -0.081619 | 0.008452 | -0.170717 | 0.245645 | 0.114026 | -0.118188 | 0.009435 | -0.039780 | 5.523477e-18 | -1.321758e-17 |
| 2425 | -0.839413 | 1.180971 | 0.418045 | -0.534405 | -0.453281 | 0.002498 | -0.237444 | 0.259108 | 0.540337 | -0.081097 | ... | -0.352021 | -0.171744 | -0.080924 | -0.032240 | -0.051236 | -0.011760 | -0.069263 | 0.093310 | -3.595846e-17 | -1.482899e-16 |
| 2426 | -3.003368 | 0.982462 | 0.742621 | -1.541234 | -0.835815 | 0.474535 | 0.860044 | 1.303658 | -0.799230 | -0.638828 | ... | -0.116141 | -0.215400 | 0.257225 | 0.105655 | -0.092238 | 0.195675 | 0.209026 | 0.045282 | 3.397196e-17 | -4.704440e-17 |
2427 rows × 39 columns
# Eigenvectors (Loadings)
eigenvectors = pca.components_
df_eigenvectors = pd.DataFrame(eigenvectors, columns=tickers, index=[f'PC{i+1}' for i in range(len(tickers))])
relevant_df_eigenvectors = df_eigenvectors.iloc[:4].round(1).T
relevant_df_eigenvectors
| PC1 | PC2 | PC3 | PC4 | |
|---|---|---|---|---|
| SP500 | -0.2 | -0.0 | -0.1 | 0.0 |
| EuroStoxx50 | -0.2 | -0.1 | 0.2 | 0.2 |
| US_Treasury_Bond_ETF | 0.1 | 0.4 | 0.0 | 0.1 |
| Corporate_Bond_ETF | -0.1 | 0.4 | 0.1 | 0.1 |
| NASDAQ | -0.2 | -0.0 | -0.1 | 0.0 |
| DAX | -0.2 | -0.0 | 0.2 | 0.2 |
| CAC40 | -0.2 | -0.1 | 0.3 | 0.2 |
| FTSE100 | -0.2 | -0.1 | 0.2 | 0.1 |
| Nikkei225 | -0.1 | -0.0 | 0.3 | 0.1 |
| HangSeng | -0.1 | -0.0 | 0.4 | -0.1 |
| ShanghaiComposite | -0.1 | -0.0 | 0.3 | -0.1 |
| BSESENSEX | -0.1 | -0.0 | 0.3 | 0.1 |
| ASX200 | -0.1 | -0.0 | 0.2 | 0.1 |
| High_Yield_Bond | -0.2 | 0.1 | -0.1 | 0.0 |
| Emerging_Markets_Bond | -0.1 | 0.2 | 0.1 | -0.0 |
| US_Treasury_Bond | 0.1 | 0.4 | 0.0 | 0.1 |
| Corporate_Bond | -0.1 | 0.4 | 0.1 | 0.1 |
| Dow_Jones | -0.2 | -0.0 | -0.1 | 0.0 |
| Russell_2000 | -0.2 | -0.0 | -0.1 | 0.0 |
| Emerging_Markets_Index | -0.2 | 0.0 | 0.1 | -0.2 |
| Total_Stock_Market | -0.2 | -0.0 | -0.1 | 0.0 |
| Gold | -0.0 | 0.2 | -0.0 | -0.5 |
| Silver | -0.1 | 0.1 | 0.0 | -0.5 |
| Crude_Oil | -0.1 | -0.1 | 0.0 | -0.3 |
| S&P_MidCap_400 | -0.2 | -0.0 | -0.1 | 0.0 |
| FTSE_Emerging | -0.2 | 0.0 | 0.1 | -0.2 |
| Total_Bond_Market | -0.0 | 0.4 | 0.0 | 0.1 |
| MSCI_World | -0.2 | -0.0 | -0.1 | 0.0 |
| Biotechnology | -0.2 | -0.0 | -0.1 | 0.1 |
| Real_Estate | -0.2 | 0.1 | -0.1 | 0.0 |
| Consumer_Staples | -0.2 | 0.0 | -0.2 | 0.1 |
| Technology | -0.2 | -0.0 | -0.1 | 0.1 |
| Health_Care | -0.2 | -0.0 | -0.2 | 0.1 |
| Utilities | -0.1 | 0.1 | -0.2 | 0.1 |
| Energy | -0.2 | -0.1 | -0.0 | -0.2 |
| Financials | -0.2 | -0.1 | -0.1 | 0.1 |
| Brazil_Stock | -0.2 | -0.0 | 0.0 | -0.2 |
| Mexico_Stock | -0.2 | 0.0 | 0.0 | -0.1 |
| Canada_Stock | -0.2 | 0.0 | 0.0 | -0.2 |
# Define the color for positive, negative, and index values
positive_color = blue
negative_color = purple_light
index_color = 'white'
# Function to assign color based on value
def color_for_value(value):
if value > 0:
return positive_color
elif value < 0:
return negative_color
else:
return soft_blue # Neutral color for zero
# Create a list of colors for each cell
cell_colors = []
for col in relevant_df_eigenvectors.columns:
colors = [color_for_value(val) for val in relevant_df_eigenvectors[col]]
cell_colors.append(colors)
# Create the Plotly table
fig = go.Figure(data=[go.Table(
header=dict(values=['Index'] + list(relevant_df_eigenvectors.columns),
fill_color='white',
align='center'),
cells=dict(values=[relevant_df_eigenvectors.index] + [relevant_df_eigenvectors[col].tolist() for col in relevant_df_eigenvectors],
fill_color=[index_color] + cell_colors, # Use the color lists here
align='center'))
])
# Update layout for aesthetics
fig.update_layout(
title="Fourth first Principal components",
title_x=0.5
)
fig.show()
fig.write_html('4PC.html')
PC1 = Market equity >0 risk vs Rates <0 vs commodities = 0 PC2 = risk geograpgique ?
# Cumulative Explained Variance Plot
cumulative_variance = np.cumsum(explained_variance_ratio) * 100
loadings = pca.components_.T * np.sqrt(pca.explained_variance_)
# Define rebalance frequency
rebalance_frequency = 'Y'
portfolio_weights_over_time = pd.DataFrame(columns=df.columns)
for period in pd.date_range(start=df_returns.index.min(), end=df_returns.index.max(), freq=rebalance_frequency):
period_data = df_returns[df_returns.index <= period]
scaler = StandardScaler()
df_standardized = scaler.fit_transform(period_data)
pca = PCA()
pca.fit(df_standardized)
# Use the first principal component to determine portfolio weights
pc1_loadings = pca.components_[0, :]
normalized_weights = pc1_loadings / np.sum(np.abs(pc1_loadings))
portfolio_weights_over_time.loc[period] = normalized_weights
portfolio_weights_over_time=portfolio_weights_over_time.set_index(portfolio_weights_over_time.index.year)
fig_scree = go.Figure()
fig_scree.add_trace(go.Bar(x=[f'PC{i+1}' for i in range(len(eigenvalues))], y=eigenvalues))
fig_scree.update_layout(title="Scree Plot of PCA", xaxis_title="Principal Components", yaxis_title="Eigenvalue")
fig_scree.show()
fig_cumulative = go.Figure()
fig_cumulative.add_trace(go.Scatter(x=[f'PC{i+1}' for i in range(len(cumulative_variance))], y=cumulative_variance, mode='lines+markers'))
fig_cumulative.update_layout(title="Cumulative Explained Variance by PCA", xaxis_title="Principal Components", yaxis_title="Cumulative Explained Variance (%)")
fig_cumulative.add_shape(
type="line",
x0=0,
y0=70,
x1=len(cumulative_variance)-1,
y1=70,
line=dict(
color=gray,
width=2,
dash="dashdot",
)
)
fig_cumulative.show()
fig_biplot = go.Figure()
tickers_ = list(tickers.values())
# Add lines and markers for loadings
for i in range(loadings.shape[0]):
fig_biplot.add_trace(go.Scatter(
x=[0, loadings[i, 0]],
y=[0, loadings[i, 1]],
mode='lines+markers+text',
#text=[None, tickers_[i]],
line=dict(color=light_purple),
marker=dict(color=blue, size=10),
showlegend=False
))
# Set axis titles
fig_biplot.update_layout(
xaxis_title="PC1 (Principal Component 1)",
yaxis_title="PC2 (Principal Component 2)",
title="PCA Biplot",
xaxis=dict(showline=True, zeroline=False, showgrid=False),
yaxis=dict(showline=True, zeroline=False, showgrid=False)
)
# Set aspect ratio to be equal for proper representation of angles between vectors
fig_biplot.update_yaxes(
scaleanchor = "x",
scaleratio = 1,
)
# Show the figure
fig_biplot.show()
fig_2d_scatter = px.scatter(df_pca, x='PC1', y='PC2', title='2D Scatter Plot of the First Two Principal Components')
fig_2d_scatter.update_layout(xaxis_title="PC1", yaxis_title="PC2")
fig_2d_scatter.show()
fig_3d_scatter = px.scatter_3d(df_pca, x='PC1', y='PC2', z='PC3', title='3D Scatter Plot of the First Three Principal Components')
fig_3d_scatter.update_layout(scene=dict(xaxis_title='PC1', yaxis_title='PC2', zaxis_title='PC3'))
fig_3d_scatter.show()
PCA_cum=(((annual_return[:12]*portfolio_weights_over_time).sum(axis=1)+1).cumprod())-1
fig_pcacum=px.line(PCA_cum)
fig_pcacum.update_layout(
xaxis_title='',
yaxis_title='',
legend_title_text='',
showlegend=False,
title = 'PCA perf cumulative',
title_x=0.5
)
pca_annual_return = (annual_return[:12]*portfolio_weights_over_time).sum(axis=1)
pca_annual_return
year 2012 -0.906317 2013 -1.084340 2014 -0.939750 2015 0.843420 2016 0.988045 2017 1.052285 2018 0.804416 2019 1.114744 2020 -0.984167 2021 -1.100323 2022 -0.849606 2023 -1.093981 dtype: float64
PCA_cum_d=(pca_annual_return+1).cumprod()
PCA_cum_d
year 2012 9.368330e-02 2013 -7.901270e-03 2014 -4.760550e-04 2015 -8.775693e-04 2016 -1.744648e-03 2017 -3.580514e-03 2018 -6.460737e-03 2019 -1.366281e-02 2020 -2.163239e-04 2021 2.170235e-05 2022 3.263895e-06 2023 -3.067448e-07 dtype: float64
import scipy.stats as stats
mean_return = df_perf_cumul_index_price_weighted.mean()
std_dev_return = df_perf_cumul_index_price_weighted.std()
def monte_carlo_simulation(daily_returns, num_days, num_simulations):
mean_return = daily_returns.mean()
std_dev_return = daily_returns.std()
simulated_performance = pd.DataFrame()
# Simulation de Monte Carlo
for i in range(num_simulations):
random_returns = np.random.normal(mean_return, std_dev_return, num_days)
cum_return = (1 + pd.Series(random_returns)).cumprod()
simulated_performance[i] = cum_return
return simulated_performance
def plot_cumulative_performance(simulated_performance):
fig = go.Figure()
for i, column in enumerate(simulated_performance.columns):
fig.add_trace(go.Scatter(x=simulated_performance.index, y=simulated_performance[column],
mode='lines', line=dict(color=colors[i % len(colors)])))
num_days = simulated_performance.index[-1]
fig.add_shape(type="line", x0=simulated_performance.index[0], y0=1,
x1=num_days, y1=1, line=dict(color='purple', width=2, dash="dashdot"))
# Mise à jour de la mise en page
fig.update_layout(
title='Monte Carlo Simulation',
xaxis_title='Days',
yaxis_title='Cumulative Returns',
legend_title_text='Simulations',
xaxis=dict(showgrid=True),
yaxis=dict(showgrid=True),
showlegend=False
)
fig.show()
return fig
daily_returns = index_price_weighted
num_days = 252
num_simulations = 100
results = monte_carlo_simulation(daily_returns, num_days, num_simulations)
MonteCarloSimplot = plot_cumulative_performance(results)
MonteCarloSimplot.write_html('MC.html')
results = monte_carlo_simulation(daily_returns, num_days, num_simulations)
sample_results_df=results.copy()
mean = sample_results_df.mean().mean()
std = sample_results_df.stack().std()
x_range = np.linspace(mean - 4*std, mean + 4*std, 100)
normal_curve = norm.pdf(x_range, mean, std)
# Create a histogram of the sample data
histogram = go.Histogram(
x=sample_results_df.stack(),
nbinsx=50,
name='Histogram of Returns',
histnorm='probability density'
)
# Create the normal distribution curve trace
curve = go.Scatter(
x=x_range,
y=normal_curve,
name='Normal Distribution Curve',
line=dict(color=gold)
)
VaR_5_percent = norm.ppf(0.05, mean, std)
CVaR_5 = mean - (std * norm.pdf(norm.ppf(0.05)) / 0.05)
GaR_95 = norm.ppf(0.95, mean, std)
CGaR_95 = mean + (std * norm.pdf(norm.ppf(0.05)) / 0.05)
# Create the VaR line trace
VaR_line = go.Scatter(
x=[VaR_5_percent, VaR_5_percent],
y=[0, max(normal_curve)],
name='VaR (5%)',
line=dict(color=gray, dash='dash')
)
CVaR_line = go.Scatter(
x=[CVaR_5, CVaR_5],
y=[0, max(normal_curve)],
name='CVaR (5%)',
line=dict(color=brown, dash='dash')
)
GaR_95_line = go.Scatter(
x=[GaR_95, GaR_95],
y=[0, max(normal_curve)],
name='GaR (5%)',
line=dict(color=turquoise, dash='dash')
)
CGaR_95_line = go.Scatter(
x=[CGaR_95, CGaR_95],
y=[0, max(normal_curve)],
name='CGaR_95 (5%)',
line=dict(color=midnight_blue, dash='dash')
)
# Combine all traces and layout for the figure
fig = go.Figure(data=[histogram, curve, VaR_line,CVaR_line,GaR_95_line,CGaR_95_line])
fig.update_layout(
title='Distribution of Simulated Returns Risk',
xaxis_title='Cumulative Returns',
yaxis_title='Probability Density',
bargap=0.01,
showlegend=False
)
# Ajouter des annotations pour la VaR et la CVaR
fig.add_annotation(x=VaR_5_percent, y=max(normal_curve), text=f"VaR (5%): {-(1-VaR_5_percent):.2%}", showarrow=False, arrowhead=1, yshift=20)
fig.add_annotation(x=CVaR_5, y=max(normal_curve), text=f"CVaR (5%): {-(1-CVaR_5):.2%}", showarrow=False, xshift=-70, yshift=-10)
fig.add_annotation(x=GaR_95, y=max(normal_curve), text=f"Max Return (5%): {(GaR_95-1):.2%}", showarrow=False, xshift=0, yshift=30)
fig.add_annotation(x=CGaR_95, y=max(normal_curve), text=f"Mean Max Return (5%): {(CGaR_95-1):.2%}", showarrow=False, xshift=120, yshift=0)
# Show the figure
fig.show()
fig.write_html('Risks.html')
def plot_efficient_frontier(df_returns):
mean_returns = df_returns.mean()
cov_matrix = df_returns.cov()
num_portfolios = 10000
results = np.zeros((3,num_portfolios))
#Création de portfolios with different weights
for i in range(num_portfolios):
weights = np.random.random(len(mean_returns))
# normalize the weights
weights /= np.sum(weights)
portfolio_return = np.sum(mean_returns * weights)
portfolio_stddev = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
results[0,i] = portfolio_return
results[1,i] = portfolio_stddev
results[2,i] = results[0,i] / results[1,i]
return results
results = pd.DataFrame(plot_efficient_frontier(df_returns).T)
fig_marko = px.scatter(results,x=0,y=1)
fig_marko.show()
def optimize_portfolio(df_returns):
data = df_returns
mean_returns = data.mean()
cov_matrix = data.cov()
num_assets = len(mean_returns)
args = (mean_returns, cov_matrix)
constraints = ({'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1})
bound = (0.0,1.0)
bounds = tuple(bound for asset in range(num_assets)) #each should be between 0,1
#Use the minimize function (scipy.optimize) to find the optimal weights that maximize the Sharpe ratio (objective function:-portfolio_return / portfolio_stddev).
result = minimize(objective_function, num_assets*[1./num_assets,], args=args,
method='SLSQP', bounds=bounds, constraints=constraints)
optimal_weights_dict = {asset: round(weight,2) for asset, weight in zip(data.columns, result.x)}
return pd.DataFrame(list(optimal_weights_dict.items()), columns=['Asset', 'Weight']).set_index(pd.DataFrame(list(optimal_weights_dict.items()), columns=['Asset', 'Weight'])['Asset']).T[1:]
def objective_function(weights, mean_returns, cov_matrix):
portfolio_return = np.sum(mean_returns * weights)
portfolio_stddev = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
return - portfolio_return / portfolio_stddev
weights=pd.DataFrame()
for period in range(0,len(df_returns),240):
datarange=df_returns.iloc[period:period+240]
# Simulate portfolio optimization to get the weights
weights_ = optimize_portfolio(datarange)
weights = pd.concat([weights,weights_], ignore_index=True)
Marko_return = (annual_return.iloc[2:13]-1) * weights.set_index(annual_return.iloc[2:13].index)
markowitz_cum = (Marko_return.sum(axis=1)+1).cumprod()
from plotly.subplots import make_subplots
import plotly.graph_objects as go
# Création d'un subplot avec 2 lignes
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.1)
# Ajout des traces pour les rendements cumulés dans la première ligne
fig.add_trace(
go.Scatter(x=df_perf_cumul_index_price_weighted.index, y=df_perf_cumul_index_price_weighted,
line=dict(width=2, color=purple), name='Price Weighted Index'),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=df_perf_cumul_index_equal_weighted.index, y=df_perf_cumul_index_equal_weighted,
line=dict(width=2, color=blue), name='Equal Weighted Index'),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=df_returns.index, y=(1 + df_returns['SP500']).cumprod(),
line=dict(width=2, color=gray), name='S&P 500 Index'),
row=1, col=1
)
fig.add_trace(
go.Scatter(x=markowitz_cum.index, y=markowitz_cum,
line=dict(width=2, color=deep_gold), name='Markowitz optimized'),
row=1, col=1
)
# Ajout des traces pour la volatilité dans la seconde ligne
fig.add_trace(
go.Scatter(x=index_price_weighted_rolling_volatility.index, y=index_price_weighted_rolling_volatility,
line=dict(width=2, color=purple), showlegend=False),
row=2, col=1
)
fig.add_trace(
go.Scatter(x=index_equal_weighted_rolling_volatility.index, y=index_equal_weighted_rolling_volatility,
line=dict(width=2, color=blue), showlegend=False),
row=2, col=1
)
fig.add_trace(
go.Scatter(x=sp_rolling_volatility.index, y=sp_rolling_volatility,
line=dict(width=2, color=gray), showlegend=False),
row=2, col=1
)
# Mise à jour des titres des axes
fig.update_xaxes(title_text="Date", row=2, col=1)
fig.update_yaxes(title_text="Cumulative Returns", row=1, col=1)
fig.update_yaxes(title_text="Volatility", row=2, col=1)
# Mise à jour de la mise en page
fig.update_layout(
title='Cumulative Returns and Volatility',
showlegend=True
)
fig.update_layout(
legend=dict(
title=dict(text='Assets'),
yanchor="top",
y=1.05,
xanchor="left",
x=0.1
),
)
# Affichage du graphique
fig.show()
fig.write_html('PerfvsVol.html')